package com.google.code.UtopiaXml;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * Application's entry point.
 *
 * @author Jiri Mauritz, Roman Lacko
 */
public class Main {

    private static final int IX_PATHOUT = 0;
    private static final int IX_STRPACK = 1;    
    private static final int IX_TYPEMAP = 2;
    
    private static String[] options = new String[3];

    private static void printHelp(PrintStream os) {
        //console window width:
        //         |--------------------------------------------------------------------------------|
        os.println("Usage: utopia-xml [options] files...");
        os.println();
        os.println("Options:");
        os.println("each option can be specified at most once");
        os.println("    --help                display this information; if used, no files are");
        os.println("                          expected.");
        os.println("-o  --output  <directory> specifies the output directory for generated files.");
        os.println("                          If not specified, files will be outputed to the");
        os.println("                          current working directory.");
        os.println("-p  --package <name>      target package of generated classes. If no package is");
        os.println("                          specified, default package is assumed.");
        os.println("-t  --typemap <name>      custom typemap, compliant to the Typemap XML Schema.");
        os.println();
        os.println("Examples:");
        os.println("utopia-xml a.xsd b.xsd c.xsd");
        os.println("    generates classes A.java, B.java, C.java in the CWD");
        os.println("utopia-xml -o outdir -p outdir a.xsd");
        os.println("    generates class A.java in directory outdir");
    }

    private static int getOptionCode(String option) {
        switch (option) {
            case "--help":
                return 1;

            case "--output":
            case "-o":
                return 2;

            case "--package":
            case "-p":
                return 3;

            case "--typemap":
            case "-t":
                return 4;

            default:
                return 0;
        }
    }
    
    /**
     * Parses command line arguments
     *
     * @param args command line arguments
     * @return list of files to parse
     */
    private static List<String> parseCmdLine(String[] args) {
        int filesIx = -1; // index of first file in args

        for (int ix = 0; (ix < args.length) && (filesIx == -1); ++ix) {
            int code = getOptionCode(args[ix]);

            switch (code) {
                case 1: // --help
                    printHelp(args.length == 1 ? System.out : System.err);
                    return null;

                case 2: // -o --output
                case 3: // -p --package
                case 4: // -t --typemap
                    ++ix;
                    if (ix >= args.length || getOptionCode(args[ix]) != 0) {
                        printHelp(System.err);
                        return null;
                    }

                    if (options[code - 2] != null) {
                        printHelp(System.err);
                        return null;
                    }
                    options[code - 2] = args[ix];
                    break;

                default: // file, stop checking options
                    filesIx = ix;
                    break;
            }
        }

        if (filesIx == -1) {
            System.err.println("utopia-xml: no input files (use --help option to display program usage)");
            return null;
        }

        List<String> files = new ArrayList<>(args.length - filesIx);

        // check files
        for (int ix = filesIx; ix < args.length; ++ix) {
            if (getOptionCode(args[ix]) != 0) {
                printHelp(System.err);
                return null;
            } else if (!Files.exists(Paths.get(args[ix]))) {
                System.err.printf("utopia-xml: the specified file '%s' does not exist\n", args[ix]);
                return null;
            }

            files.add(args[ix]);
        }

        return files;
    }

    /**
     * Program entry
     *
     * usage: utopia-xml [options] files...
     *
     * @param args program arguments
     */
    public static void main(String[] args) /* throws IOException, XPathExpressionException */ {

        List<String> files = parseCmdLine(args);

        if (files == null) {
            return;
        }

        // load custom typemap if necessary
        if (options[IX_TYPEMAP] != null) {
            try {
                TypeConverter.setTypemap(options[IX_TYPEMAP]);
            } catch (ParserConfigurationException | SAXException | IOException ex) {
                System.err.println("utopia-xml: failed to load custom typemap");
                ex.printStackTrace(System.err);
                return;
            }
        }

        // create directories
        if (options[IX_PATHOUT] != null) {
            try {
                Files.createDirectories(Paths.get(options[IX_PATHOUT]));
            } catch (IOException ex) {
                System.err.printf("utopia-xml: failed to create output directory '%s':\n", options[IX_PATHOUT]);
                ex.printStackTrace(System.err);
                return;
            }
        } else {
            options[IX_PATHOUT] = ".";
        }

        // Run transformations
        for (String filename : files) {
            try {

                Path xsd_src = Paths.get(filename);
                Path xsd_dst = Paths.get(options[IX_PATHOUT], xsd_src.getFileName().toString());

                // copy XSD schema file if neccessary
                if (!Files.exists(xsd_dst)) {
                    Files.copy(xsd_src, xsd_dst, StandardCopyOption.COPY_ATTRIBUTES);
                }

                DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = domFactory.newDocumentBuilder();
                Document doc = builder.parse(xsd_src.toString());

                File out = new File(options[IX_PATHOUT]);

                SchemaParser schemaParser = new SchemaParser(doc);
                Schema schema = schemaParser.generateClases();
                schema.setSchemaName(xsd_dst.getFileName().toString());

                for (ClassTemplate ct : schema.getClasses()) {
                    ClassBuilder clb = ct.generateClass(options[IX_STRPACK]);
                    clb.writeToFile(out);
                }
                
                String parsername = "Parser";
                if (files.size() > 1 ) {                    
                    String[] parts = xsd_src.getFileName().toString().split("[.]");
                    
                    for (String s : parts) { // protection agains filenames like "....foo.xsd"
                        if (!s.isEmpty()) {
                            parsername = CodeBuilder.toUpperCamelCase(s) + parsername;
                            break;
                        }
                    }                                                                                
                }
                                
                if (Files.exists(Paths.get(parsername + ".java"))) {
                    System.err.printf("warning: parser '%s' already exists\n", parsername);                
                }
                
                ParserGenerator parser = new ParserGenerator(schema, parsername);
                ClassBuilder clb = parser.generateParserClass(options[IX_STRPACK]);
                clb.writeToFile(out);
            } catch (IOException | ParserConfigurationException | SAXException | XPathExpressionException ex) {
                System.err.printf("utopia-xml: error when building class '%s':\n", filename);
                ex.printStackTrace(System.err);
                return;
            }
        }
    }
}
